#include <utils/traceUtils/outputUtils.idc>
#include <utils/traceUtils/jumpRule.idc>
#include <utils/traceUtils/segmentUtils.idc>
#include <utils/traceUtils/systemConfig.idc>
/**
 * 属性：
 * public:
 *      this.canDo            代表是否初始化完成，值为0或者1，为0代表初始化失败，为1代表初始化成功
 * private:
 *      this.sp               输出汇编代码指令最后行数
 *      this.outputUtils      输出工具类 
 *      this.startPid         进程id
 *      this.startTid         线程id
 *      this.ASL_addr         偏移地址
 *      this.text_min_addr    主程序text段开始地址
 *      this.text_max_addr    主程序text段结束地址
 * 方法：
 * public:
 *      trace();                                        开始跑trace的方法
 *      addBlackFunc(name);                             添加c方法的黑名单；name是方法名字符串
 *      addMsgSendBlackFunc(name);                      添加oc方法的黑名单；name是方法名字符串
 *      addRunToX30Addr(addr);                          添加跳转到X30的地址
 *      addForRunToAddrByUser(startAddr, endAddr);      添加地址跳转方法，效果当执行到startAddr方法时直接执行到endAddr；startAddr是开始跳转的地址，endAddr是跳转到的地址
*/
class TraceHook{
    /**
     * 构造方法参数说明
     * stopAddr               trace end address (no offset address)
     * sleepTime              step sleep time (s)
     * moduleName             execution binary file name and output file name
     * baseOuputFolderPath    base output folder path (folder must exist)
     */
    TraceHook(stopAddr, sleepTime, moduleName, baseOuputFolderPath){
        this.stopAddr = stopAddr;
        this.sleepTime = sleepTime;
        this.moduleName = moduleName; 
        this.baseOuputFolderPath = baseOuputFolderPath;

        Message("TraceHook init start\n");
        this.canDo = 1;
        this.sp = 0;

        // 初始化跳转规则
        this.jumpRule = JumpRule();
        this.systemConfig = SystemConfig();
        this.systemConfig.configForJumpAddr(this.jumpRule);
        this.systemConfig.configAddMsgSendBlackFunc(this.jumpRule);
        this.systemConfig.configBlackFunc(this.jumpRule);

        // 获取text段的首末地址
        if(this.determineTextSegment() != 1){
            Warning("No offset found ！！！exit ！！！");
            this.canDo = 0;
            return;
        }

        // 初始化输出类
        this.outputUtils = OutputUtils(this.ASL_addr, this.baseOuputFolderPath, this.moduleName);
        if(this.outputUtils.canDo != 1){
            Warning("output init exception ！！！exit ！！！");
            this.canDo = 0;
            return;
        }
        auto logContent = form("[%s] asl addr [0x%x], text_min_addr [0x%x], text_max_addr [0x%x], real stop addr [0x%x]\n", this.moduleName, this.ASL_addr, this.text_min_addr, this.text_max_addr, this.stopAddr + this.text_min_addr);
        Message(logContent);
        this.outputUtils.writeToFile(logContent);

        // 获取当前断点的进程id和线程id
        this.startPid = GetEventPid();
        this.startTid = GetEventTid();
        Message("TraceHook pid [%x] tid [%x]\n", this.startPid, this.startTid);
        Message("TraceHook init end\n");
    }

    ~TraceHook(){
        Message("get trace end!\n");
    }

    trace(){
        auto code, addr;
        StepInto();
        // EnableTracing(TRACE_STEP, 1);

        code = GetDebuggerEvent(WFNE_ANY | WFNE_CONT, -1);
        for( ;code > 0; code = GetDebuggerEvent(WFNE_ANY | WFNE_CONT, -1)){
            addr = GetEventEa();
            if(addr == this.stopAddr + this.text_min_addr && GetEventPid() == this.startPid && GetEventTid() == this.startTid){
                break;
            }

            if(addr == 0x3BDBA78 + this.text_min_addr && GetEventPid() == this.startPid && GetEventTid() == this.startTid){
                break;
            }

            // Message("addr 0x%x\n", addr);
            if(this.jumpRule.isRunToX30(addr) != -1){
                Message("runToX30AddrArray contain 1  addr : 0x%x \n", addr);
                this.jumpRule.runToX30();
                continue;
            }

            if(addr < this.text_min_addr || addr > this.text_max_addr){
                this.jumpRule.goBeyondAddrHandle(addr, this.text_min_addr, this.text_max_addr);
                continue;
            }

            this.sp = this.sp + 1;
            this.jumpRule.addrAddBBlJumpFunc(addr);
            if(this.jumpRule.checkStepOver(addr) != -1){
                this.outputUtils.outputByAddr(addr, this.sp); 
                Sleep(this.sleepTime * 1000);
                StepOver();
                continue;
            }

            this.outputUtils.outputByAddr(addr, this.sp);
            Sleep(this.sleepTime * 1000);
            StepInto();
        }
        // EnableTracing(TRACE_STEP, 0);
    }

    addBlackFunc(name){
        this.jumpRule.addBlackFunc(name);
    }

    addMsgSendBlackFunc(name){
        this.jumpRule.addMsgSendBlackFunc(name);
    }

    addRunToX30Addr(addr){
        this.jumpRule.addRunToX30Addr(addr);
    }

    addForRunToAddrByUser(startAddr, endAddr){
        this.jumpRule.addForRunToAddrByUser(startAddr, endAddr);
    }

    determineTextSegment(){
        auto minAddr, maxAddr;
        minAddr = this.jumpRule.segmentUtils.getStartAddrBySegmentName(this.moduleName);
        if(minAddr == 0xffffffffffffffff){
            return 0;
        }

        maxAddr = this.jumpRule.segmentUtils.getEndAddrBySegmentName(this.moduleName);
        if(maxAddr == 0xffffffffffffffff){
            return 0;
        }

        this.text_min_addr = minAddr;
        this.text_max_addr = maxAddr;
        this.ASL_addr = minAddr - 0x100000000;
        return 1;
    }
};